////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   Vegetation.cpp
//  Version:     v1.00
//  Created:     28/5/2001 by Vladimir Kajalin
//  Compilers:   Visual Studio.NET
//  Description: Loading trees, buildings, ragister/unregister entities for rendering
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"

#include "terrain.h"
#include "StatObj.h"
#include "ObjMan.h" 
#include "CullBuffer.h"
#include "3dEngine.h"
#include "Vegetation.h"

//volatile int g_lockVegetationPhysics = 0;

float CVegetation::g_scBoxDecomprTable[256] _ALIGN(128);

// g_scBoxDecomprTable heps on consoles (no difference on PC)
void CVegetation::InitVegDecomprTable()
{
	const float cDecompFactor = (VEGETATION_CONV_FACTOR / 255.f);
	for(int i=0; i<256; ++i)
		g_scBoxDecomprTable[i] = (float)i * cDecompFactor;
}

CVegetation::CVegetation() 
{
	Init();

	GetInstCount(eERType_Vegetation)++;
}

void CVegetation::Init()
{
	m_nObjectTypeID=0;
	m_vPos.Set(0,0,0);
	m_ucScale=0;
	m_pPhysEnt=0;
	m_ucAngle = 0;
	m_ucSunDotTerrain = 255;
	m_pRNTmpData = NULL;
	m_pSpriteInfo = NULL;

  m_HMAIndex = 0;
  m_alignNormal[0]=m_alignNormal[1]=127;
#ifdef SEG_WORLD
  m_bApplyPhys = false;
  m_nStaticTypeSlot = 0;
#endif

  // assert(sizeof(CVegetation) == 64 || sizeof(m_pRNTmpData) != 4);
}

//////////////////////////////////////////////////////////////////////////
void CVegetation::CalcMatrix( Matrix34A &tm, int * pFObjFlags )
{
  FUNCTION_PROFILER_3DENGINE;

	Matrix34A matTmp;
	int orFlags = 0;

	matTmp.SetIdentity();

	if (float fAngle = GetZAngle())
	{
		matTmp.SetRotationZ( fAngle );
		orFlags |= FOB_TRANS_ROTATE;
	}

	StatInstGroup * vegetGroup = GetStatObjGroup();

	if(vegetGroup->bAlignToTerrain)
	{
    Vec3 vTerrainNormal;

    if(!GetCVars()->e_VoxTer || m_bEditor)
    {
			//All paths from GetTerrainSurfaceNormal() return a normalized vector
			Vec3 vTempTerrainNormal = GetTerrain()->GetTerrainSurfaceNormal(m_vPos, 0.5f, vegetGroup->bAffectedByVoxels, m_nSID);
      vTempTerrainNormal = LERP(Vec3(0,0,1.f), vTempTerrainNormal, GetCVars()->e_VegetationAlignToTerrainAmount);
      vTerrainNormal = vTempTerrainNormal.GetNormalizedFloat();
      m_alignNormal[0] = (byte)SATURATEB((0.5f + 0.5f*vTerrainNormal.x)*255.f);
      m_alignNormal[1] = (byte)SATURATEB((0.5f + 0.5f*vTerrainNormal.y)*255.f);
    }
    else
    {
      vTerrainNormal.x = ((float)m_alignNormal[0] - 127.f) / 127.f;
      vTerrainNormal.y = ((float)m_alignNormal[1] - 127.f) / 127.f;
      vTerrainNormal.z = sqrtf(1.f - vTerrainNormal.x*vTerrainNormal.x + vTerrainNormal.y*vTerrainNormal.y);
    }

		Vec3 vDir = Vec3(-1,0,0).Cross(vTerrainNormal);
		
		Matrix33 m33;

		m33 = m33.CreateOrientation(vDir, -vTerrainNormal, 0);

		matTmp = m33 * matTmp;

		orFlags |= FOB_TRANS_ROTATE;
	}

  float fScale = GetScale();
	if (fScale != 1.0f)
	{
		Matrix33 m33; 
		m33.SetIdentity();
		m33.SetScale(Vec3(fScale,fScale,fScale));
		matTmp = m33*matTmp;
		orFlags |= FOB_TRANS_SCALE;
	}

	matTmp.SetTranslation( m_vPos );

	if(pFObjFlags)
	{
		*pFObjFlags |= (FOB_TRANS_TRANSLATE|orFlags);
	}

	tm = matTmp;
}

//////////////////////////////////////////////////////////////////////////
AABB CVegetation::CalcBBox()
{
  AABB WSBBox = GetStatObj()->GetAABB();
  //WSBBox.min.z = 0;

	if (m_ucAngle)
	{
		Matrix34A tm;
		CalcMatrix( tm );
		WSBBox.SetTransformedAABB( tm, WSBBox );
	}
	else
	{
    float fScale = GetScale();
		WSBBox.min = m_vPos + WSBBox.min*fScale;
		WSBBox.max = m_vPos + WSBBox.max*fScale;
	}

  SetBBox(WSBBox);

  return WSBBox;
}

void CVegetation::CalcTerrainAdaption()
{
	FUNCTION_PROFILER_3DENGINE;

  assert(m_pRNTmpData);

  AABB WSBBox = GetBBox();

	const	f32 minX		=	WSBBox.min.x;
	const	f32 minY		=	WSBBox.min.y;
	const	f32 maxX		=	WSBBox.max.x;
	const	f32 maxY		=	WSBBox.max.y;
	f32	HeightMax	=	m_vPos.z;
	f32	HeightMin	=	m_vPos.z;
	float H0,H1,H2,H3;
	H0=H1=H2=H3=m_vPos.z;
  if(minX>=0.f && maxX<CTerrain::GetTerrainSize() && (!GetCVars()->e_VoxTer || m_bEditor))
	{
    StatInstGroup * vegetGroup = GetStatObjGroup();
		H0	=	Get3DEngine()->GetTerrainElevation(maxX,m_vPos.y,false);
		H1	=	Get3DEngine()->GetTerrainElevation(minX,m_vPos.y,false);
	}
	if(minY>=0.f && maxY<CTerrain::GetTerrainSize() && (!GetCVars()->e_VoxTer || m_bEditor))
	{
    StatInstGroup * vegetGroup = GetStatObjGroup();
    H2	=	Get3DEngine()->GetTerrainElevation(m_vPos.x,maxY,false);
		H3	=	Get3DEngine()->GetTerrainElevation(m_vPos.x,minY,false);
	}

  CRNTmpData::SRNUserData & userData = m_pRNTmpData->userData;

	HeightMax		=		max(max(H0,H1),max(H2,H3));
	HeightMin		=		min(min(H0,H1),min(H2,H3));
	m_HMARange	=		max(max(HeightMax-m_vPos.z,m_vPos.z-HeightMin),FLT_EPSILON);

	m_HMAIndex	|=0xfff;	//to round up...ceil... the integer part
	m_HMAIndex++;

	const float RANGE	=	static_cast<float>((1<<2)-1);
	int H0Idx		=		static_cast<int>((H0-m_vPos.z)/m_HMARange*RANGE+RANGE+0.5f);//0.5for rounding
	int H1Idx		=		static_cast<int>((H1-m_vPos.z)/m_HMARange*RANGE+RANGE+0.5f);
	int H2Idx		=		static_cast<int>((H2-m_vPos.z)/m_HMARange*RANGE+RANGE+0.5f);
	int H3Idx		=		static_cast<int>((H3-m_vPos.z)/m_HMARange*RANGE+RANGE+0.5f);

	m_HMAIndex	=		(m_HMAIndex&0xfffff000)|H0Idx|(H1Idx<<3)|(H2Idx<<6)|(H3Idx<<9);

//	m_WSBBox.min.z+=HeightMin-m_vPos.z;
//	m_WSBBox.max.z+=HeightMax-m_vPos.z;
}

//////////////////////////////////////////////////////////////////////////
void CVegetation::Render(SRenderObjectModifier * pROM, int nThreadId, SSectorTextureSet * pTerrainTexInfo) const
{	
	FUNCTION_PROFILER_3DENGINE;
#if defined(ENABLE_VEGETATION_PROFILER)
	Get3DEngine()->IncreaseVegetationRenderCounter();
#endif

	StatInstGroup * vegetGroup = GetStatObjGroup();

	CStatObj * pStatObj = vegetGroup->GetStatObj();

	if(!pStatObj)
		return;

//  if(vegetGroup.bAffectedByVoxels && Get3DEngine()->m_pVoxTerrain)
  //  return;

	CRenderObject * & pRenderObject = m_pRNTmpData->userData.pRenderObject;	
	if( !pRenderObject ) 
		return;

	SRenderObjData *pOD = NULL;
	if (m_HMAIndex)
	{
		pOD = GetRenderer()->EF_GetObjData(pRenderObject, true);
		pOD->m_HMAData	=	m_HMAIndex;
	}

	// Physicalized foliage might have not found the correct stencil ref during onrendernodebecomevisible 
	// because it can be called from the physics callback. 
	// A query for the visareastencilref is therefore issued every time it is rendered.
	pRenderObject->m_nVisAreaStencilRef = 0;
	if(m_pOcNode && m_pOcNode->m_pVisArea)
		pRenderObject->m_nVisAreaStencilRef = gEnv->pRenderer->EF_GetVisAreaStencilRef( (IVisArea*)m_pOcNode->m_pVisArea );

	uint8 nDissolveRefA = 0;
	int nLodA, nLodB;

	int minUsableLod = pStatObj->GetMinUsableLod();
	int maxUsableLod = (int)pStatObj->m_nMaxUsableLod;
	nLodA = CLAMP(m_pRNTmpData->userData.nLod, minUsableLod, maxUsableLod);
	nLodA = pStatObj->FindNearesLoadedLOD(nLodA);
	nLodB = -1;

	if(!m_nRenderStackLevel && !pROM && !Get3DEngine()->IsRenderingIntoShadowMap())
	{
		const Vec3 vCamPos = GetCamera().GetPosition();

		float fEntDistance2D = cry_sqrtf(vCamPos.GetSquaredDistance2D(m_vPos))*m_fZoomFactor;

		const float fSpriteSwitchDist = GetSpriteSwitchDist();

		if(GetCVars()->e_Dissolve)
		{
			int nLod;
			float fSwitchRange = min(fSpriteSwitchDist * GetCVars()->e_DissolveSpriteDistRatio, GetCVars()->e_DissolveSpriteMinDist);
			bool bUsingSprite = vegetGroup->bUseSprites && (fEntDistance2D > (fSpriteSwitchDist - fSwitchRange));
			if (bUsingSprite)
			{
				nLod = -1;
			}
			else
			{
				nLod = nLodA;
			}

			SLodDistDissolveTransitionState *pLodDistDissolveTransitionState = &m_pRNTmpData->userData.lodDistDissolveTransitionState;

			if(pLodDistDissolveTransitionState->nOldLod != -1 &&
				pLodDistDissolveTransitionState->nNewLod != -1 &&
				nLod == -1)
			{
				nLod = nLodA;
			}

			// when we first load before streaming we get a lod of -1. When a lod streams in
			// we kick off a transition to N, but without moving there's nothing to continue the transition.
			// Catch this case when we claim to be in lod -1 but have no sprite info, and snap.
			if(pLodDistDissolveTransitionState->nOldLod == -1 && !m_pSpriteInfo)
				pLodDistDissolveTransitionState->nOldLod = pLodDistDissolveTransitionState->nNewLod;

			float fDissolve = GetObjManager()->GetLodDistDissolveRef(pLodDistDissolveTransitionState, fEntDistance2D, nLod);

			nDissolveRefA = (uint8)(255.f * SATURATE(fDissolve));
			nLodA = pLodDistDissolveTransitionState->nOldLod;
			nLodB = pLodDistDissolveTransitionState->nNewLod;

			minUsableLod = min(minUsableLod, -1); // allow for sprites.
    
			nLodA = CLAMP(nLodA, minUsableLod, maxUsableLod);
			nLodB = CLAMP(nLodB, minUsableLod, maxUsableLod);
		}

		float fRenderQuality = vegetGroup->bUseSprites ? 
		  min(1.f, max(1.f - fEntDistance2D/GetSpriteSwitchDist(),0.f)):min(1.f, max(1.f - fEntDistance2D/(m_fWSMaxViewDist*0.3333f),0.f));
		pRenderObject->m_nRenderQuality = (uint16)(fRenderQuality * 65535.0f);  

		if(m_pSpriteInfo)
		{
			m_pSpriteInfo->ucDissolveOut = 255;

			if(nLodA == -1 && nLodB != -1)
			{
				m_pSpriteInfo->ucAlphaTestRef = nDissolveRefA;
			}
			else if(nLodB == -1 && nLodA != -1)
			{
				m_pSpriteInfo->ucAlphaTestRef = nDissolveRefA;
				m_pSpriteInfo->ucDissolveOut = 0;
			}
			else if(nLodA == -1 && nLodB == -1)
			{
				m_pSpriteInfo->ucAlphaTestRef = 0;
			}
			else
			{
				m_pSpriteInfo->ucAlphaTestRef = 255;
			}
		}
	}

	if(m_pSpriteInfo && m_pSpriteInfo->ucAlphaTestRef < 255 && GetCVars()->e_VegetationSprites && !Get3DEngine()->IsRenderingIntoShadowMap())
	{
		PodArray<SVegetationSpriteInfo>& arrSpriteInfo = GetObjManager()->m_arrVegetationSprites[m_nRenderStackLevel][max(nThreadId,(int)0)];
		arrSpriteInfo.Add(*m_pSpriteInfo);
	}

	AABB bbox;
	FillBBox_NonVirtual(bbox);

	// update LOD faster in zoom mode
	if(!m_nRenderStackLevel && m_nZoomMode)
	{
		const Vec3 vCamPos = GetCamera().GetPosition();
		float fEntDistance = cry_sqrtf(Distance::Point_AABBSq(vCamPos,bbox))*m_fZoomFactor;
		m_pRNTmpData->userData.nLod = CObjManager::GetObjectLOD(fEntDistance, GetLodRatioNormalized(), bbox.GetRadius());
	}

	if(m_bEditor) // editor can modify material anytime
	{
		if (vegetGroup->pMaterial)
			pRenderObject->m_pCurrMaterial = vegetGroup->pMaterial;
		else 
			pRenderObject->m_pCurrMaterial = pStatObj->GetMaterial();
	}

	const Vec3 vCamPos = GetCamera().GetPosition();
	const Vec3 vObjCenter = bbox.GetCenter();
	const Vec3 vObjPos = GetPos();

	if(m_dwRndFlags & ERF_NO_DECALNODE_DECALS)
		pRenderObject->m_ObjFlags |= FOB_NO_STATIC_DECALS;

	// check the object against the water level
	if(CObjManager::IsAfterWater(vObjCenter, vCamPos, Get3DEngine()->GetWaterLevel()))
		pRenderObject->m_ObjFlags |= FOB_AFTER_WATER;
	else
		pRenderObject->m_ObjFlags &= ~FOB_AFTER_WATER;

	CRenderObject * pRenderObjectFin = pRenderObject;
	bool duplicated = false; 

	if(m_pRNTmpData->userData.m_pFoliage) // fix for vegetation flicker when switching back from foliage mode
	{
		pRenderObjectFin = GetRenderer()->EF_DuplicateRO(pRenderObject,NULL);
		if (SRenderObjData *objData = GetRenderer()->EF_GetObjData(pRenderObjectFin, false))
		{
			if (!objData->m_pCharInstance) 
			{
				pRenderObjectFin->m_ObjFlags &= ~FOB_CHARACTER; 
			}
			else
			{
				pRenderObjectFin->m_ObjFlags |= FOB_CHARACTER; 
			}
		}
		duplicated = true; 
	}

	if(GetCVars()->e_Dissolve && !m_pSpriteInfo)
	{		
		float fDissolveDist = CLAMP(0.1f*m_fWSMaxViewDist, GetFloatCVar(e_DissolveDistMin), GetFloatCVar(e_DissolveDistMax));
    
		const float fDissolveStartDist = sqr(m_fWSMaxViewDist-fDissolveDist);

		float fEntDistanceSq = Distance::Point_AABBSq(vCamPos,bbox)*sqr(m_fZoomFactor);

		if(fEntDistanceSq > fDissolveStartDist)
		{
			float fDissolve = (sqrt(fEntDistanceSq) - (m_fWSMaxViewDist-fDissolveDist))
				/ fDissolveDist;
			nDissolveRefA = (uint8)(255.f * SATURATE(fDissolve));
			nLodB = -1;
		}
	}

	pStatObj->SetupBending(pRenderObjectFin, this, duplicated);

	if (Get3DEngine()->IsTessellationAllowed(pRenderObjectFin))
	{
		// Allow this RO to be tessellated, however actual tessellation will be applied if enabled in material
		pRenderObjectFin->m_ObjFlags |= FOB_ALLOW_TESSELLATION;
	}
	else
		pRenderObjectFin->m_ObjFlags &= ~FOB_ALLOW_TESSELLATION;
	pStatObj->RenderInternal(pRenderObjectFin, pROM, 0, nThreadId, nLodA, nDissolveRefA, nLodB, 255-nDissolveRefA);

	if(GetCVars()->e_BBoxes && !Get3DEngine()->IsRenderingIntoShadowMap())
    GetObjManager()->RenderObjectDebugInfo((IRenderNode*)this, pRenderObject);
}

//////////////////////////////////////////////////////////////////////////

IStatObj * CVegetation::GetEntityStatObj( unsigned int nPartId, unsigned int nSubPartId, Matrix34A * pMatrix, bool bReturnOnlyVisible ) 
{ 
	if(nPartId!=0)
		return 0;

	if(pMatrix)
	{
		if(m_pRNTmpData)
		{
		  *pMatrix = m_pRNTmpData->userData.objMat;
		}
		else
		{
			  Matrix34A tm;
			  CalcMatrix( tm );
			  *pMatrix = tm;
		}
	}

  return GetStatObjGroup()->GetStatObj(); 
}

float CVegetation::GetMaxViewDist()
{
	StatInstGroup *group = GetStatObjGroup();
	CStatObj *pStatObj = (CStatObj*)(IStatObj*)group->pStatObj;
	if  (pStatObj)
	{
		if (GetMinSpecFromRenderNodeFlags(m_dwRndFlags) == CONFIG_DETAIL_SPEC)
			return max(GetCVars()->e_ViewDistMin, group->fVegRadius*GetScale()*GetCVars()->e_ViewDistRatioDetail*GetViewDistRatioNormilized());

		return max(GetCVars()->e_ViewDistMin, group->fVegRadius*GetScale()*GetCVars()->e_ViewDistRatioVegetation*GetViewDistRatioNormilized());
	}
	
	return 0;
}

float CVegetation::GetSpriteSwitchDist() const
{
	StatInstGroup *vegetGroup = GetStatObjGroup();

	return vegetGroup->m_fSpriteSwitchDist*LERP(1.0f, CVegetation::GetScale(), GetFloatCVar(e_VegetationSpritesScaleFactor));
}

void CVegetation::Physicalize( bool bInstant )
{
	FUNCTION_PROFILER_3DENGINE;

	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_Physics, 0, "Vegetation physicalization");
	
	StatInstGroup * vegetGroup = GetStatObjGroup();

	CStatObj * pBody = vegetGroup->GetStatObj();
	if(!pBody)
		return;

	bool bHideability = vegetGroup->bHideability;
	bool bHideabilitySecondary = vegetGroup->bHideabilitySecondary;

	//////////////////////////////////////////////////////////////////////////
	// Not create instance if no physical geometry.
	if(!pBody->m_arrPhysGeomInfo[PHYS_GEOM_TYPE_DEFAULT] && !(pBody->GetFlags() & STATIC_OBJECT_COMPOUND))
		//no bHidability check for the E3 demo - make all the bushes with MAT_OBSTRUCT things soft cover 
		//if(!(pBody->m_arrPhysGeomInfo[PHYS_GEOM_TYPE_OBSTRUCT] && (bHideability || pBody->m_nSpines)))
		if(!(pBody->m_arrPhysGeomInfo[PHYS_GEOM_TYPE_OBSTRUCT]))
			if(!(pBody->m_arrPhysGeomInfo[PHYS_GEOM_TYPE_NO_COLLIDE]))
				return;
	//////////////////////////////////////////////////////////////////////////

#ifdef SEG_WORLD
	m_bApplyPhys = true;
#endif

  AABB WSBBox = GetBBox();

//	WriteLock lock(g_lockVegetationPhysics);
	int bNoOnDemand = 
		!GetCVars()->e_OnDemandPhysics || max(WSBBox.max.x-WSBBox.min.x, WSBBox.max.y-WSBBox.min.y)>Get3DEngine()->GetCVars()->e_OnDemandMaxSize;
	if (bNoOnDemand)
		bInstant = true;

  /*if (!bInstant)
  {
    pe_status_placeholder spc;
    if (m_pPhysEnt && m_pPhysEnt->GetStatus(&spc) && spc.pFullEntity)
      GetSystem()->GetIPhysicalWorld()->DestroyPhysicalEntity(spc.pFullEntity);

    pe_params_bbox pbb;
    pbb.BBox[0] = m_WSBBox.min;
    pbb.BBox[1] = m_WSBBox.max;
    pe_params_foreign_data pfd;
    pfd.pForeignData = (IRenderNode*)this;
    pfd.iForeignData = PHYS_FOREIGN_ID_STATIC;
		pfd.iForeignFlags = PFF_VEGETATION;

    if (!m_pPhysEnt)
      m_pPhysEnt = GetSystem()->GetIPhysicalWorld()->CreatePhysicalPlaceholder(PE_STATIC,&pbb);
    else
      m_pPhysEnt->SetParams(&pbb);
    m_pPhysEnt->SetParams(&pfd);
    return;
  }*/

//  pBody->CheckLoaded();

	if (!bInstant)
	{
		gEnv->pPhysicalWorld->RegisterBBoxInPODGrid(&WSBBox.min);
		return;
	}

  

  // create new
	//pe_status_placeholder spc;
	//int bOnDemandCallback = 0;
	pe_params_pos pp;
	Matrix34A mtx;	CalcMatrix(mtx);
	Matrix34 mtxNA = mtx;
	pp.pMtx3x4 = &mtxNA;
	float fScale = GetScale();
	//pp.pos = m_vPos;
  //pp.q.SetRotationXYZ( Ang3(0,0,GetZAngle()) );
	//pp.scale = fScale;
  if (!m_pPhysEnt)
  {
    m_pPhysEnt = GetSystem()->GetIPhysicalWorld()->CreatePhysicalEntity(PE_STATIC, (1-bNoOnDemand)*5.0f, &pp,
			(IRenderNode*)this, PHYS_FOREIGN_ID_STATIC);        
    if(!m_pPhysEnt)
      return;
  }	else
		m_pPhysEnt->SetParams(&pp,1);
	//else if (bOnDemandCallback = m_pPhysEnt->GetStatus(&spc) && !spc.pFullEntity)
  // GetSystem()->GetIPhysicalWorld()->CreatePhysicalEntity(PE_STATIC,5.0f,0,0,0,-1,m_pPhysEnt);

	pe_action_remove_all_parts remove_all;
	m_pPhysEnt->Action(&remove_all,1);

	pe_geomparams params;
	params.density = 800;
	params.flags |= geom_break_approximation;
	params.scale = fScale;

	if (pBody->GetFlags() & STATIC_OBJECT_COMPOUND)
		pBody->Physicalize(m_pPhysEnt, &params);

	// collidable
	if(pBody->m_arrPhysGeomInfo[PHYS_GEOM_TYPE_DEFAULT])
	{
		pe_params_ground_plane pgp;
		Vec3 bbox[2] = { pBody->GetBoxMin(),pBody->GetBoxMax() };
		pgp.iPlane = 0;
		pgp.ground.n.Set(0,0,1);
		(pgp.ground.origin = (bbox[0]+bbox[1])*0.5f).z -= (bbox[1].z-bbox[0].z)*0.49f;
		pgp.ground.origin *= fScale;



		m_pPhysEnt->SetParams(&pgp,1);
			
		if (pBody->GetFlags() & STATIC_OBJECT_NO_PLAYER_COLLIDE)
			params.flags&=~geom_colltype_player;

		if (!(m_dwRndFlags & ERF_PROCEDURAL)) 
		{
			params.idmatBreakable = pBody->m_idmatBreakable;
			if (pBody->m_bBreakableByGame)
				params.flags |= geom_manually_breakable;
		} else
			params.idmatBreakable = -1;
    m_pPhysEnt->AddGeometry(pBody->m_arrPhysGeomInfo[PHYS_GEOM_TYPE_DEFAULT], &params, -1, 1);
	}

	phys_geometry *pgeom;
	params.density = 2;
	params.idmatBreakable = -1;
	if(pBody->m_arrPhysGeomInfo[PHYS_GEOM_TYPE_NO_COLLIDE] && pBody->m_arrPhysGeomInfo[PHYS_GEOM_TYPE_OBSTRUCT])
	{
		params.minContactDist = GetFloatCVar(e_FoliageStiffness);
		params.flags = geom_squashy|geom_colltype_obstruct; 
		m_pPhysEnt->AddGeometry(pBody->m_arrPhysGeomInfo[PHYS_GEOM_TYPE_OBSTRUCT], &params, 1024, 1);

		if (GetCVars()->e_PhysFoliage>=2) 
		{
			params.density = 0;
			params.flags = geom_log_interactions;
			params.flagsCollider = 0;
			if (pBody->m_nSpines)
				params.flags |= geom_colltype_foliage_proxy;
			m_pPhysEnt->AddGeometry(pBody->m_arrPhysGeomInfo[PHYS_GEOM_TYPE_NO_COLLIDE], &params, 2048, 1);
		}
	}	
	else if ((pgeom=pBody->m_arrPhysGeomInfo[PHYS_GEOM_TYPE_NO_COLLIDE]) || (pgeom=pBody->m_arrPhysGeomInfo[PHYS_GEOM_TYPE_OBSTRUCT]))
	{
		params.minContactDist = GetFloatCVar(e_FoliageStiffness);
		params.flags = geom_log_interactions|geom_squashy;
		if (pBody->m_arrPhysGeomInfo[PHYS_GEOM_TYPE_OBSTRUCT])
			params.flags |= geom_colltype_obstruct;
		if (pBody->m_nSpines)
			params.flags |= geom_colltype_foliage_proxy;
		m_pPhysEnt->AddGeometry(pgeom, &params, 1024, 1);
	}

  if(bHideability)
  {
    pe_params_foreign_data  foreignData;
    m_pPhysEnt->GetParams(&foreignData);
    foreignData.iForeignFlags |= PFF_HIDABLE;
    m_pPhysEnt->SetParams(&foreignData,1);
  }

	if(bHideabilitySecondary)
	{
		pe_params_foreign_data  foreignData;
		m_pPhysEnt->GetParams(&foreignData);
		foreignData.iForeignFlags |= PFF_HIDABLE_SECONDARY;
		m_pPhysEnt->SetParams(&foreignData,1);
	}

	//PhysicalizeFoliage();
}

bool CVegetation::PhysicalizeFoliage(bool bPhysicalize, int iSource, int nSlot)
{
	CStatObj *pBody = GetStatObj();
	if(!pBody || !pBody->m_pSpines)
		return false;

	if (bPhysicalize)
	{
    Get3DEngine()->CheckCreateRNTmpData(&m_pRNTmpData, this);
    assert(m_pRNTmpData);

		Matrix34A mtx; CalcMatrix(mtx);
		if (pBody->PhysicalizeFoliage(m_pPhysEnt, mtx, m_pRNTmpData->userData.m_pFoliage, GetCVars()->e_FoliageBranchesTimeout, iSource) 
			)//&& !pBody->m_arrPhysGeomInfo[PHYS_GEOM_TYPE_DEFAULT])
			((CStatObjFoliage*)m_pRNTmpData->userData.m_pFoliage)->m_pVegInst = this;
	} 
  else if (m_pRNTmpData && m_pRNTmpData->userData.m_pFoliage)
  {
    m_pRNTmpData->userData.m_pFoliage->Release();
    m_pRNTmpData->userData.m_pFoliage = NULL;
  }

  UpdateFoliageState();

	return m_pRNTmpData && m_pRNTmpData->userData.m_pFoliage;
}

IRenderNode * CVegetation::Clone() const
{
	CVegetation * pDestVeg = new CVegetation();

	//CVegetation member vars
	pDestVeg->m_vPos						= m_vPos;
	pDestVeg->m_nObjectTypeID		= m_nObjectTypeID;
	pDestVeg->m_ucAngle					= m_ucAngle;
	pDestVeg->m_ucSunDotTerrain	= m_ucSunDotTerrain;
	pDestVeg->m_ucScale					= m_ucScale;
	pDestVeg->m_boxExtends[0]		= m_boxExtends[0];
	pDestVeg->m_boxExtends[1]		= m_boxExtends[1];
	pDestVeg->m_boxExtends[2]		= m_boxExtends[2];
	pDestVeg->m_boxExtends[3]		= m_boxExtends[3];
	pDestVeg->m_boxExtends[4]		= m_boxExtends[4];
	pDestVeg->m_boxExtends[5]		= m_boxExtends[5];
	pDestVeg->m_ucRadius				= m_ucRadius;
	pDestVeg->m_HMAIndex				= m_HMAIndex;

	//IRenderNode member vars
	//	We cannot just copy over due to issues with the linked list of IRenderNode objects
	pDestVeg->m_fWSMaxViewDist			= m_fWSMaxViewDist;
	pDestVeg->m_dwRndFlags					= m_dwRndFlags;
	pDestVeg->m_pOcNode							= m_pOcNode;
	pDestVeg->m_ucViewDistRatio			= m_ucViewDistRatio;
	pDestVeg->m_ucLodRatio					= m_ucLodRatio;
	pDestVeg->m_nInternalFlags			= m_nInternalFlags;
	pDestVeg->m_nMaterialLayers			= m_nMaterialLayers;
	//pDestVeg->m_pRNTmpData				//If this is copied from the source render node, there are two
	//	pointers to the same data, and if either is deleted, there will
	//	be a crash when the dangling pointer is used on the other

	return pDestVeg;
}

void CVegetation::ShutDown()
{
	Dephysicalize( );
	Get3DEngine()->FreeRenderNodeState(this); // Also does unregister entity.

	if(m_pRNTmpData)
		Get3DEngine()->FreeRNTmpData(&m_pRNTmpData);
	assert(!m_pRNTmpData);
	
	SAFE_DELETE(m_pSpriteInfo);
}

CVegetation::~CVegetation()
{
	ShutDown();

	GetInstCount(eERType_Vegetation)--;

  Get3DEngine()->m_lstKilledVegetations.Delete(this);
}

void CVegetation::Dematerialize( )
{
}

void CVegetation::Dephysicalize( bool bKeepIfReferenced )
{
	// delete old physics
//	WriteLock lock(g_lockVegetationPhysics);
	if(m_pPhysEnt && GetSystem()->GetIPhysicalWorld()->DestroyPhysicalEntity(m_pPhysEnt,4*(int)bKeepIfReferenced))
	{
		m_pPhysEnt = 0;
    if(m_pRNTmpData && m_pRNTmpData->userData.m_pFoliage)
    {
      m_pRNTmpData->userData.m_pFoliage->Release();
      m_pRNTmpData->userData.m_pFoliage = NULL;
    }
	}

  //UpdateFoliageState();
}

void CVegetation::GetMemoryUsage(ICrySizer * pSizer) const
{
	SIZER_COMPONENT_NAME(pSizer, "Vegetation");
	pSizer->AddObject(this, sizeof(*this));
	if (m_pSpriteInfo)
		pSizer->Add(*m_pSpriteInfo);
}

void CVegetation::UpdateRndFlags()
{
	StatInstGroup * vegetGroup = GetStatObjGroup();

	const uint32 dwFlagsToUpdate = 
		ERF_CASTSHADOWMAPS | ERF_CASTSHADOWINTORAMMAP | ERF_HIDABLE | ERF_PICKABLE | ERF_SPEC_BITS_MASK | ERF_USE_TERRAIN_COLOR | ERF_INTEGRATION_TYPE_BIT_1 | ERF_INTEGRATION_TYPE_BIT_2 | ERF_AFFECTED_BY_VOXELS | ERF_OUTDOORONLY | ERF_NO_DECALNODE_DECALS;
	m_dwRndFlags &= ~dwFlagsToUpdate;
	m_dwRndFlags |= vegetGroup->m_dwRndFlags & dwFlagsToUpdate;

  SetLodRatio((int)(vegetGroup->fLodDistRatio * 100.f));
  SetViewDistRatio((int)(vegetGroup->fMaxViewDistRatio * 100.f));
}

void CVegetation::UpdateSunDotTerrain()
{
  float fRadius = GetBBox().GetRadius();
	Vec3 vTerrainNormal = GetTerrain()->GetTerrainSurfaceNormal(m_vPos, fRadius, false, m_nSID);
	m_ucSunDotTerrain	 = (uint8)(CLAMP((vTerrainNormal.Dot(Get3DEngine()->GetSunDirNormalized()))*255.f, 0, 255));
}

const char *CVegetation::GetName() const
{
	return (GetStatObjGroupSize() && GetStatObj() )? 
		GetStatObj()->GetFilePath() : "StatObjNotSet";
}
//////////////////////////////////////////////////////////////////////////
IRenderMesh *		CVegetation::GetRenderMesh(int nLod)
{
	CStatObj*	pStatObj(GetStatObj());

	if (!pStatObj)
	{
		return NULL;
	}

	pStatObj = (CStatObj*)pStatObj->GetLodObject(nLod);

	if (!pStatObj)
	{
		return NULL;
	}

	return pStatObj->GetRenderMesh();
}
//////////////////////////////////////////////////////////////////////////
IMaterial* CVegetation::GetMaterial(Vec3 * pHitPos)
{
	StatInstGroup * vegetGroup = GetStatObjGroup();

	if(vegetGroup->pMaterial)
		return vegetGroup->pMaterial;

	if (CStatObj *pBody = vegetGroup->GetStatObj())
		return pBody->GetMaterial();

	return NULL;
}

IMaterial* CVegetation::GetMaterialOverride()
{
	StatInstGroup * vegetGroup = GetStatObjGroup();

	if(vegetGroup->pMaterial)
		return vegetGroup->pMaterial;

	return NULL;
}

/*
float CVegetation::GetLodForDistance(float fDistance)
{
	StatInstGroup * vegetGroup = GetStatObjGroup();

	assert(GetSpriteSwitchDist());

	float fSpriteSwitchDist = GetSpriteSwitchDist();

	CStatObj * pBody = vegetGroup.GetStatObj();

//	if(fDistance > fSpriteSwitchDist)
//		if(((CStatObj*)pBody->GetLodObject(pBody->m_nLoadedLodsNum-1))->GetRenderTrisCount()>8)
	//		return -1;

	float fLod = (fDistance/fSpriteSwitchDist)*pBody->m_nLoadedLodsNum;

	if(fLod > pBody->m_nLoadedLodsNum-1)
		fLod = (float)(pBody->m_nLoadedLodsNum-1);

	return fLod;
}
*/
float CVegetation::GetZAngle() const 
{ 
	if(m_ucAngle)
	{
		return DEG2RAD( 360.0f/255.0f*m_ucAngle ); 
	}

	return 0;
}

void CVegetation::OnRenderNodeBecomeVisible()
{
  assert(m_pRNTmpData);
  CRNTmpData::SRNUserData & userData = m_pRNTmpData->userData;

  Matrix34A mtx;
  CalcMatrix( mtx );
  userData.objMat = mtx;
  float fRadius = GetBBox().GetRadius();
  
  if(!GetCVars()->e_VoxTer || m_bEditor)
    CalcTerrainAdaption();

  // init pRenderObject

  CRenderObject * & pObj = userData.pRenderObject;

  assert(!pObj);

  pObj = GetRenderer()->EF_GetObject_Perm();

  pObj->m_pRenderNode = this;
  pObj->m_II.m_Matrix = mtx;  

	// fixme: vis areas appear not working currently with vegetation
	//pObj->m_nVisAreaStencilRef = 

  pObj->m_II.m_AmbColor = Get3DEngine()->GetSkyColor();
  pObj->m_fAlpha = 1.f;
  pObj->m_ObjFlags |= FOB_INSHADOW | FOB_VEGETATION | FOB_TRANS_MASK;

  if( !userData.objMat.m01 && !userData.objMat.m02 && !userData.objMat.m10 && !userData.objMat.m12 && !userData.objMat.m20 && !userData.objMat.m21 )
    pObj->m_ObjFlags &= ~FOB_TRANS_ROTATE;
  else
    pObj->m_ObjFlags |= FOB_TRANS_ROTATE;

  ////////////////////////////////////////////////////////////////////////////////////////////////////
  // Set render quality
  ////////////////////////////////////////////////////////////////////////////////////////////////////

  const Vec3 vCamPos = GetCamera().GetPosition();
  float fEntDistance = cry_sqrtf(Distance::Point_AABBSq(vCamPos,GetBBox()))*m_fRealZoomFactor;

  pObj->m_nSort = fastround_positive(fEntDistance*2.0f);

  StatInstGroup * vegetGroup = GetStatObjGroup();

  pObj->m_nMaterialLayers = vegetGroup->nMaterialLayers;

	bool bUseTerrainColor((vegetGroup->bUseTerrainColor && GetCVars()->e_VegetationUseTerrainColor) || GetCVars()->e_VegetationUseTerrainColor==2);
  if (bUseTerrainColor)
    pObj->m_ObjFlags |= FOB_BLEND_WITH_TERRAIN_COLOR;
  else
    pObj->m_ObjFlags &= ~FOB_BLEND_WITH_TERRAIN_COLOR;

  if( uint8 nMaterialLayers = GetMaterialLayers() ) 
  {
    uint8 nFrozenLayer = ( nMaterialLayers&MTL_LAYER_FROZEN )? MTL_LAYER_FROZEN_MASK: 0;
    uint8 nWetLayer = ( nMaterialLayers&MTL_LAYER_WET )? MTL_LAYER_WET_MASK: 0;
    pObj->m_nMaterialLayers = (uint32) (nFrozenLayer<< 24) | (nWetLayer<<16);
  }
  else
    pObj->m_nMaterialLayers = 0;

  if (vegetGroup->pMaterial)
    pObj->m_pCurrMaterial = vegetGroup->pMaterial;
  else
  if (vegetGroup->GetStatObj())
    pObj->m_pCurrMaterial = vegetGroup->GetStatObj()->GetMaterial();

  if(bUseTerrainColor)
    pObj->m_II.m_AmbColor.a = (1.0f/255.f*m_ucSunDotTerrain);
  else
    pObj->m_II.m_AmbColor.a = vegetGroup->fBrightness;

	if(m_pOcNode && m_pOcNode->m_pVisArea)
		pObj->m_nVisAreaStencilRef = gEnv->pRenderer->EF_GetVisAreaStencilRef( (IVisArea*)m_pOcNode->m_pVisArea );
	else
		pObj->m_nVisAreaStencilRef = 0;

  if(pObj->m_II.m_AmbColor.a > 1.f)
    pObj->m_II.m_AmbColor.a = 1.f;

  UpdateBending();

  float fEntDistance2D = cry_sqrtf(vCamPos.GetSquaredDistance2D(m_vPos))*m_fRealZoomFactor;
  UpdateRenderQuality(fEntDistance2D);

	if(vegetGroup->bUseTerrainColor)
		UpdateSunDotTerrain();

  userData.nLod = CObjManager::GetObjectLOD(fEntDistance, GetLodRatioNormalized(), fRadius);
	
	int nLod = userData.nLod;

	const float fSpriteSwitchDist = GetSpriteSwitchDist();
	float fSwitchRange = min(fSpriteSwitchDist * GetCVars()->e_DissolveSpriteDistRatio, GetCVars()->e_DissolveSpriteMinDist);

	if(fEntDistance2D > (fSpriteSwitchDist - fSwitchRange) && fSpriteSwitchDist+GetFloatCVar(e_DissolveDistband) < m_fWSMaxViewDist)
		nLod = -1;

	userData.lodDistDissolveTransitionState.nNewLod = userData.lodDistDissolveTransitionState.nOldLod = nLod;
	userData.lodDistDissolveTransitionState.fStartDist = 0.0f;
	userData.lodDistDissolveTransitionState.bFarside = false;
}

void CVegetation::UpdateRenderQuality(float fDistance)
{
  if(!m_pRNTmpData)
    return;

  if(CRenderObject * pObj = m_pRNTmpData->userData.pRenderObject)
  {
    StatInstGroup * vegetGroup = GetStatObjGroup();
    float fRenderQuality = vegetGroup->bUseSprites ? 
      min(1.f, max(1.f - fDistance/GetSpriteSwitchDist(),0.f)) : 
		  min(1.f, max(1.f - fDistance/(m_fWSMaxViewDist*0.3333f),0.f));
    
			pObj->m_nRenderQuality = (uint16)(fRenderQuality * 65535.0f);
  }
}

void CVegetation::UpdateFoliageState()
{
  if(!m_pRNTmpData)
    return;

  if(CRenderObject * pObj = m_pRNTmpData->userData.pRenderObject)
  {
    IFoliage * pFoliage = GetFoliage();

    if(pFoliage && ((CStatObjFoliage*)pFoliage)->m_pVegInst == this)
    {
      SRenderObjData *pOD = GetRenderer()->EF_GetObjData(pObj, true);
      if (pOD)
      {
        pOD->m_pCharInstance = pFoliage;
        pObj->m_ObjFlags |= FOB_CHARACTER|FOB_VEGETATION;
        pFoliage->SetFlags(pFoliage->GetFlags() & ~IFoliage::FLAG_FROZEN | -(int)(pObj->m_nMaterialLayers&MTL_LAYER_FROZEN) & IFoliage::FLAG_FROZEN);
      }
    }
    else
    {
      SRenderObjData *pOD = GetRenderer()->EF_GetObjData(pObj, false);
      pObj->m_ObjFlags &= ~(FOB_CHARACTER);
      if (pOD)
        pOD->m_pCharInstance = NULL;
    }
  }
}

void CVegetation::UpdateBending()
{
  const StatInstGroup* vegetGroup = GetStatObjGroup();
	if (GetCVars()->e_VegetationBending)
	{
		// main bending scale (not affecting detail bending)
		// size relative scale causing some inconsistency problems in current levels
		// userData.m_Bending.m_fMainBendingScale = min(0.5f * vegetGroup.fBending / (vegetGroup.fVegRadiusVert * GetScale()), 1.f);
	  CRNTmpData::SRNUserData & userData = m_pRNTmpData->userData;
		userData.m_fMainBendingScale = vegetGroup->fBending;
	}
}

void CVegetation::AddBending(Vec3 const& v) 
{ 
  if(m_pRNTmpData)
    m_pRNTmpData->userData.vCurrentWind += v; 
}

const AABB CVegetation::GetBBox() const
{ 
  AABB aabb;
  FillBBoxFromExtends(aabb, m_boxExtends, m_vPos); 
  return aabb;
}

void CVegetation::FillBBox(AABB & aabb)
{
  FillBBoxFromExtends(aabb, m_boxExtends, m_vPos); 
}

const float CVegetation::GetRadius() const
{
  const float *const __restrict cpDecompTable = g_scBoxDecomprTable;
  return cpDecompTable[m_ucRadius];
}

void CVegetation::SetBBox( const AABB& WSBBox )
{ 
	SetBoxExtends(m_boxExtends, &m_ucRadius, WSBBox, m_vPos);
}

void CVegetation::UpdateSpriteInfo(SVegetationSpriteInfo & si, float fSpriteAmount, SSectorTextureSet * pTerrainTexInfo) const
{
  const Vec3 vCamPos = GetCamera().GetPosition();
  StatInstGroup * vegetGroup = GetStatObjGroup();

	const float nMin = 1;
	const float nMax = 255;

	IF(GetCVars()->e_Dissolve,1)
	{
		si.ucAlphaTestRef = SATURATEB((int)((1.f-fSpriteAmount)*nMax + fSpriteAmount*nMin));
	}
	else
		si.ucAlphaTestRef = (byte)nMin;

  si.pTerrainTexInfo = vegetGroup->bUseTerrainColor ? pTerrainTexInfo : NULL;
  si.pVegetation = this;

	Matrix34A matRotZ;

	float fZAngle = GetZAngle();
	if (fZAngle != 0.0f)
	{
		matRotZ.SetRotationZ(fZAngle);
	}
	else
	{
		matRotZ.SetIdentity();
	}

  Vec3 vCenter = vegetGroup->GetStatObj()->GetVegCenter() * GetScale();
	vCenter = matRotZ * vCenter;
  Vec3 vD1 = m_vPos + vCenter - vCamPos;

	float fAngle = cry_atan2f(vD1.x, vD1.y) + fZAngle;
  int dwAngle = ((uint32)(fAngle*(255.0f/(2*g_PI)))) & 0xff;
  si.ucSlotId = (((dwAngle+FAR_TEX_HAL_ANGLE)*FAR_TEX_COUNT)/256)%FAR_TEX_COUNT;
  assert(si.ucSlotId<FAR_TEX_COUNT);

  si.sp = Sphere(GetPos()+vCenter, GetScale()*vegetGroup->fVegRadius);

  si.ucSunDotTerrain = m_ucSunDotTerrain;
  si.pLightInfo = &vegetGroup->m_arrSSpriteLightInfo[si.ucSlotId];

  si.pStatInstGroup = vegetGroup;
  si.fScaleV = GetScale()*si.pStatInstGroup->fVegRadiusVert;
  si.fScaleH = GetScale()*si.pStatInstGroup->fVegRadiusHor;
};

IFoliage *CVegetation::GetFoliage(int nSlot) 
{
  if(m_pRNTmpData)
    return m_pRNTmpData->userData.m_pFoliage;

  return 0;
}

IPhysicalEntity* CVegetation::GetBranchPhys(int idx, int nSlot) 
{ 
  IFoliage * pFoliage = GetFoliage();
  return pFoliage && (unsigned int)idx < (unsigned int)((CStatObjFoliage*)pFoliage)->m_nRopes ? ((CStatObjFoliage*)pFoliage)->m_pRopes[idx] : 0; 
}

void CVegetation::OffsetPosition(const Vec3& delta)
{
#ifdef SEG_WORLD
	if (m_pRNTmpData) m_pRNTmpData->OffsetPosition(delta);
	// GetBBox before moving position
	AABB aabb = GetBBox();
	if(m_bApplyPhys)
		gEnv->pPhysicalWorld->UnregisterBBoxInPODGrid(&aabb.min);
	m_vPos += delta;
	aabb.Move(delta);
	// SetBBox after new position is applied
	SetBBox(aabb);
	if(m_bApplyPhys)
		gEnv->pPhysicalWorld->RegisterBBoxInPODGrid(&aabb.min);
#endif
}
